package maggames.core.base;

import java.util.Properties;

import maggames.core.GameMove;
import maggames.core.GameState;
import maggames.core.GameTurn;
import maggames.core.Piece;
import maggames.core.Player;
import maggames.core.exceptions.InvalidTurnException;
import maggames.core.exceptions.UnIntializedException;
import maggames.core.interfaces.GameBoard;
import maggames.core.interfaces.GameEngine;

import org.apache.log4j.Logger;

/**
 * This abstract class representing a basic game engine.  Simple and common methods and objects have been created
 * using "base" objects.  This class should be extended for all future games.
 * 
 * @author BenjaminPLee
 * @version 1.0
 */
public class BaseGameEngine implements GameEngine {
	private static final Logger log = Logger.getLogger(BaseGameEngine.class);

	/**
	 * Width property constants
	 * <br>TODO: externalize to properties file
	 */
	public final String widthProp = "width";
	
	/**
	 * Height property constant
	 * <br>TODO: externalize to properties file
	 */
	public final String heightProp = "height";
	
	/**
	 * Default width for base game board
	 * <br>TODO: externalize to properties file
	 */
	public final String widthDefault = "5";
	
	/**
	 * Default height for base game board
	 * <br>TODO: externalize to properties file
	 */
	public final String heightDefault = "5";
	
	/**
	 * BaseGameBoard object
	 */
	protected BaseGameBoard board;
	
	/**
	 * The player whose turn it currently is, NONE if in setup, init, etc
	 */
	protected Player currentPlayer;
	
	/**
	 * The current game state
	 */
	protected GameState gameState;
	
	/**
	 * Check to see if the engine has been initialized.
	 */
	protected boolean initialized;
	
	/**
	 * Default Constructor
	 */
	public BaseGameEngine() {
		currentPlayer = Player.NONE;
		gameState = GameState.INIT;
		initialized = false;
	}
	
	/* (non-Javadoc)
	 * @see maggames.core.interfaces.GameEngine#cleanUpGame()
	 */
	public void cleanUpGame() {
		log.debug("Cleaning up BaseGameEngine");
		
		checkInitialized("cleanUpGame()");
		
		board = null;
		currentPlayer = null;
		gameState = null;
		initialized = false;
	}

	/* (non-Javadoc)
	 * @see maggames.core.interfaces.GameEngine#getCurrentPlayer()
	 */
	public Player getCurrentPlayer() {
		checkInitialized("getCurrentPlayer()");
		return currentPlayer;
	}

	/* (non-Javadoc)
	 * @see maggames.core.interfaces.GameEngine#getGameBoard()
	 */
	public GameBoard getGameBoard() {
		checkInitialized("getGameBoard()");
		return board;
	}

	/* (non-Javadoc)
	 * @see maggames.core.interfaces.GameEngine#getGameState()
	 */
	public GameState getGameState() {
		checkInitialized("getGameState()");
		return gameState;
	}

	/* (non-Javadoc)
	 * @see maggames.core.interfaces.GameEngine#initialize(java.util.Properties)
	 */
	public void initialize(Properties props) {
		log.debug("Initializing BaseGameEngine");
		
		String width = props.getProperty(widthProp, widthDefault);
		String height = props.getProperty(heightProp, heightDefault);
		
		board = new BaseGameBoard(Integer.parseInt(width), Integer.parseInt(height));
		currentPlayer = Player.NONE;
		gameState = GameState.INIT;
		
		initialized = true;

		log.debug("BaseGameEngine initialized with height of " + height + " and width of " + width);
	}
	
	/**
	 * Initialization check utility method.
	 * 
	 * @param methodSignature String representing the calling method for logging purposes
	 * @throws UnIntializedException thrown if the class was not properly initialized prior to the method call
	 */
	protected void checkInitialized(String methodSignature) {
		if(!initialized) {
			log.error("BaseGameEngine was not initialized prior to use - " + methodSignature + ".");
			throw new UnIntializedException(this.getClass());
		}
	}
	
	/**
	 * Throws an InvalidTurnException if the wrong player has attempted to make a move.
	 * 
	 * @param mover Player attempting to make a move
	 * @throws InvalidTurnException thrown to inform calling code of an invalid turn
	 */
	protected void checkPlayerForTurn(Player mover) throws InvalidTurnException {
		switch(currentPlayer) {
			case NONE:
				throw new InvalidTurnException("Invalid current player. Player [" + mover + "] attempted to make a move while current player = NONE.");
			case PLAYER1:
			case PLAYER2: // intentional fall through
					if(currentPlayer != mover)
						throw new InvalidTurnException("Invalid current player and mover. Not equal.");
				break;
			case BOTH:
				if(mover == Player.NONE)
					throw new InvalidTurnException("Invalid mover : NONE.");
				break;
			default:
				throw new InvalidTurnException("Invalid current player.");
		}
	}
	
	/**
	 * Throws an InvalidTurnExceptio if the turn was made in the wrong game state.
	 * 
	 * @throws InvalidTurnException
	 */
	protected void checkGameStateForTurn() throws InvalidTurnException {
		switch(gameState) {
			case INIT:
				throw new InvalidTurnException("Invalid game state.  Turns can not be made during a INIT state.");
			case SETUP:
			case TURNS: // intentional fall through
				break;
			case WIN:
				throw new InvalidTurnException("Invalid game state.  Turns can not be made during a WIN state.");
			default:
				throw new InvalidTurnException("Invalid game state.");
		}
	}

	/**
	 * Checks to see if the given piece can be controled by the given player.
	 * 
	 * @param player
	 * @param piece
	 * @throws InvalidTurnException
	 */
	protected void checkPieceForPlayer(Player player, Piece piece) throws InvalidTurnException {
		switch(piece) {
		case NONE:
			throw new InvalidTurnException("No player can control piece of type NONE.");
		case P1:
		case P1SPECIAL: // intentional fallthrough
			if(player != Player.PLAYER1 && player != Player.BOTH)
				throw new InvalidTurnException("Must be Player1 to control P1 or P1Special pieces.");
			break;
		case P2:
		case P2SPECIAL: // intentional fallthrough
			if(player != Player.PLAYER2 && player != Player.BOTH)
				throw new InvalidTurnException("Must be Player2 to control P2 or P2Special pieces.");
			break;
		case COMMON:
			break; // do nothing, either player can control
		default:
			throw new InvalidTurnException("Invalid piece type.");
		}
	}
	
	/**
	 * Checks to make sure the piece moved was moved to an adjacent square (next to
	 * the original).
	 * 
	 * @param move move object being checked
	 * @param allowDiagonal should diagonal moves be allowed
	 * @throws InvalidTurnException
	 */
	protected void checkMoveAdjacent(GameMove move, boolean allowDiagonal) throws InvalidTurnException {
		checkMoveOnBoard(move);
		
		int xDiff = Math.abs(move.getX2() - move.getX1());
		int yDiff = Math.abs(move.getY2() - move.getY1());
		
		if(xDiff > 1 || yDiff > 1 || (xDiff == 0 && yDiff == 0)) {
			throw new InvalidTurnException("Invalid move.  Move should be adjacent. " + xDiff + ":" + yDiff);
		}
		
		if(!allowDiagonal && xDiff == 1 && yDiff == 1) {
			throw new InvalidTurnException("Invalid move.  Diagonal moves not allowed. " + xDiff + ":" + yDiff);
		}
	}
	
	/**
	 * Checks to see if move was a valid single jump (move piece over oposing piece in
	 * a straight line to the next available open square).
	 * 
	 * @param move move object being checked
	 * @param allowDiagonal should diagonal moves be allowed
	 * @throws InvalidTurnException
	 */
	protected void checkMoveJump(GameMove move, boolean allowDiagonal) throws InvalidTurnException {
		throw new InvalidTurnException("checkMoveJump IS NOT YET IMPLEMENTED!");
		// TODO
	}

	/**
	 * Checks to see if the move was in a straight line from the original location.
	 * 
	 * @param move
	 * @param allowDiagonal
	 * @return the number of spaces moved.  Adjacent moves = 1
	 * @throws InvalidTurnException
	 */
	protected int checkMoveInStraightLine(GameMove move, boolean allowDiagonal) throws InvalidTurnException {
		throw new InvalidTurnException("checkMoveInStraightLine IS NOT YET IMPLEMENTED!");
		//return 0; // TODO
	}

	/**
	 * Checks to see if the location moved to was open
	 * 
	 * @param move
	 * @throws InvalidTurnException
	 */
	protected void checkOnePiecePerTile(GameMove move) throws InvalidTurnException {
		if(board.getTile(move.getX2(), move.getY2()) != Piece.NONE)
			throw new InvalidTurnException("End location of move was not empty.");
	}
	
	/**
	 * Checks to see if the moved piece and the piece on the board match.
	 * 
	 * @param move
	 * @throws InvalidTurnException
	 */
	protected void checkCorrectPieceTypeMoved(GameMove move) throws InvalidTurnException {
		if(board.getTile(move.getX1(), move.getY1()) != move.getPiece())
			throw new InvalidTurnException("Incorrect type of piece reported in move.");
	}
	
	/**
	 * Checks to see that the location is offboard.
	 * 
	 * @param x
	 * @param y
	 * @throws InvalidTurnException
	 */
	protected void checkLocationOffBoard(int x, int y) throws InvalidTurnException {
		if(x != GameMove.OFFBOARD && y != GameMove.OFFBOARD)
			throw new InvalidTurnException("Given location (" + x + "," + y + ") was NOT off board.");
	}
	
	/**
	 * Checks to make sure the location is on the board.
	 * 
	 * @param x
	 * @param y
	 * @throws InvalidTurnException
	 */
	protected void checkLocationOnBoard(int x, int y) throws InvalidTurnException {
		if(x == GameMove.OFFBOARD || y == GameMove.OFFBOARD)
			throw new InvalidTurnException("Given location (" + x + "," + y + ") was off board.");
	}
	
	/**
	 * Checks to make sure both starting and destination locations are on the board.
	 * 
	 * @param move move being checked
	 * @throws InvalidTurnException
	 */
	protected void checkMoveOnBoard(GameMove move) throws InvalidTurnException {
		checkLocationOnBoard(move.getX1(), move.getY1());
		checkLocationOnBoard(move.getX2(), move.getY2());
	}
	
	/**
	 * Checks to see if the given player and piece match up according to the getPlayerPiece method
	 * 
	 * @param player
	 * @param piece
	 * @throws InvalidTurnException
	 */
	protected void checkCorrectPlayerPiece(Player player, Piece piece) throws InvalidTurnException {
		if(piece != getPlayerPiece(player)) {
			throw new InvalidTurnException("Incorrect piece being moved.");
		}
	}
	
	/**
	 * Returns the standard piece for a given player.
	 * 
	 * @param player
	 * @return standard piece associated with the given player
	 */
	protected Piece getPlayerPiece(Player player) {
		if(player == Player.PLAYER1) {
			return Piece.P1;
		}
		
		if(player == Player.PLAYER2) {
			return Piece.P2;
		}

		return Piece.NONE;
	}
	

	public Player checkForWin() {
		log.info("Non implemented method being called on BaseGameEngine.");
		checkInitialized("checkForWin");
		return Player.NONE;
	}
	

	public boolean checkTurn(GameTurn turn) {
		log.info("Non implemented method being called on BaseGameEngine.");
		checkInitialized("checkturn");
		return false;
	}
	

	public boolean takeTurn(GameTurn turn) {
		log.info("Non implemented method being called on BaseGameEngine.");
		checkInitialized("taketurn");
		return false;
	}
}
